﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Projekt.Models.Primitives;
using Projekt.Cameras;

namespace Projekt.Models
{
    public class CustomModel : DrawableGameComponent
    {
        private GraphicsDevice graphicsDevice;
        private Engine game;
        
        private Vector3 position;
        public Vector3 Position
        {
            get { return position; }
            set
            {
                Vector3 moved = value - position;
                this.boundingBox.Min += moved;
                this.boundingBox.Max += moved;
                position = value;
            }
        }
        private Vector3 rotation;
        public Vector3 Rotation
        {
            get { return rotation; }
            set
            {
                buildBoundingBox();
                rotation = value;
            }
        }
        private Vector3 scale;
        public Vector3 Scale
        {
            get { return scale; }
            set
            {
                buildBoundingBox();
                scale = value;
            }
        }

        public string modelName;
        public Model Model { get; private set; }
        private Matrix[] modelTransforms;
        Matrix baseWorld;

        // Bounding objects
        private BoundingSphere boundingSphere;
        public BoundingSphere BoundingSphere
        {
            get
            {
                // No need for rotation, as this is a sphere
                Matrix worldTransform = Matrix.CreateScale(Scale) * Matrix.CreateTranslation(Position);

                BoundingSphere transformed = boundingSphere;
                transformed = transformed.Transform(worldTransform);

                return transformed;
            }
        }

        private BoundingBox boundingBox;
        public BoundingBox BoundingBox
        {
            get
            {
                return boundingBox;
            }
        }

        public List<BoundingBox> boundingBoxs;

        public List<Primitive> Components;

        public CustomModel(string modelName, Vector3 Position, Vector3 Rotation, Vector3 Scale, GraphicsDevice graphicsDevice, Engine game)
            : base(game)
        {
            this.modelName = modelName;
            this.Model = game.Content.Load<Model>("Models/" + modelName); ;
            this.graphicsDevice = graphicsDevice;
            this.game = game;
            this.game.Components.Add(this);

            modelTransforms = new Matrix[Model.Bones.Count];
            Model.CopyAbsoluteBoneTransformsTo(modelTransforms);

            this.Position = Position;
            this.Rotation = Rotation;
            this.Scale = Scale;

            buildBoundingSphere();
            buildBoundingBox();

            Components = new List<Primitive>();
            Components.Add(new Sphere(this.BoundingSphere.Center, new Vector3(this.BoundingSphere.Radius), Color.Red, graphicsDevice, game));
            Components.Add(new Box(this.boundingBox.Min, this.boundingBox.Max, Color.Green, graphicsDevice, game));
        }

        private void buildBoundingSphere()
        {
            BoundingSphere sphere = new BoundingSphere(Vector3.Zero, 0);

            //Merge all the model's built in bounding spheres
            foreach (ModelMesh mesh in Model.Meshes)
            {
                BoundingSphere transformed = mesh.BoundingSphere.Transform(modelTransforms[mesh.ParentBone.Index]);

                sphere = BoundingSphere.CreateMerged(sphere, transformed);
            }

            this.boundingSphere = sphere;
        }

        private void buildBoundingBox()
        {
            baseWorld = Matrix.CreateScale(Scale) * Matrix.CreateFromYawPitchRoll(Rotation.Y, Rotation.X, Rotation.Z) * Matrix.CreateTranslation(Position);
            //boundingBoxs = new List<BoundingBox>();

            // Initialize minimum and maximum corners of the bounding box to max and min values
            Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

            // For each mesh of the model
            foreach (ModelMesh mesh in Model.Meshes)
            {
                Vector3 _min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
                Vector3 _max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {
                    Matrix localWorld = modelTransforms[mesh.ParentBone.Index] *
                baseWorld;
                    // Vertex buffer parameters
                    int vertexStride = meshPart.VertexBuffer.VertexDeclaration.VertexStride;
                    int vertexBufferSize = meshPart.NumVertices * vertexStride;

                    // Get vertex data as float
                    float[] vertexData = new float[vertexBufferSize / sizeof(float)];
                    meshPart.VertexBuffer.GetData<float>(vertexData);

                    // Iterate through vertices (possibly) growing bounding box, all calculations are done in world space
                    for (int i = 0; i < vertexBufferSize / sizeof(float); i += vertexStride / sizeof(float))
                    {
                        Vector3 transformedPosition = Vector3.Transform(new Vector3(vertexData[i], vertexData[i + 1], vertexData[i + 2]), localWorld);

                        min = _min = Vector3.Min(min, transformedPosition);
                        max = _max = Vector3.Max(max, transformedPosition);
                    }
                }
                //boundingBoxs.Add(new BoundingBox(_min, _max));
            }
            this.boundingBox = new BoundingBox(min, max);
        }

        public override void Update(GameTime gameTime)
        {
            if (Components[1].Visible)
            {
                Components[1].Position = BoundingBox.Min + (BoundingBox.Max - BoundingBox.Min) / 2f;
                Components[1].Scale = new Vector3(BoundingBox.Max.X - BoundingBox.Min.X, BoundingBox.Max.Y - BoundingBox.Min.Y, BoundingBox.Max.Z - BoundingBox.Min.Z);
            }
            if (Components[0].Visible)
            {
                Components[0].Position = BoundingSphere.Center;
                Components[0].Scale = new Vector3(BoundingSphere.Radius);
            }

            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
            if (game.camera.BoundingVolumeIsInView(this.BoundingBox))
            {
                baseWorld = Matrix.CreateScale(Scale) * Matrix.CreateFromYawPitchRoll(Rotation.Y, Rotation.X, Rotation.Z) * Matrix.CreateTranslation(Position);

                foreach (ModelMesh mesh in Model.Meshes)
                {
                    Matrix localWorld = modelTransforms[mesh.ParentBone.Index] * baseWorld;

                    foreach (ModelMeshPart meshPart in mesh.MeshParts)
                    {
                        BasicEffect effect = (BasicEffect)meshPart.Effect;

                        effect.World = localWorld;
                        effect.View = game.camera.View;
                        effect.Projection = game.camera.Projection;
                        effect.EnableDefaultLighting();
                    }

                    mesh.Draw();
                }
            }
            
            base.Draw(gameTime);
        }

        public override string ToString()
        {
            return modelName;
            //return base.ToString();
        }
    }
}
